package sofile

import (
	"errors"
	"io/ioutil"
	"os"

	"gitee.com/kimsoso/go-utils/utils"
	"github.com/vmihailenco/msgpack/v5"
)

type blockIndex struct {
	Offset int64 `yaml:"o"`
	Size   int64 `yaml:"s"`
}

type blockIndexFile struct {
	path   string
	blocks map[string]*blockIndex
}

func newIndexFile(path string) *blockIndexFile {
	b := &blockIndexFile{path: path, blocks: map[string]*blockIndex{}}
	b._load()
	return b
}

func (b *blockIndexFile) clearBlocks() {
	b.blocks = map[string]*blockIndex{}
}

func (b *blockIndexFile) _load() {
	buf, _ := ioutil.ReadFile(b.path)
	if len(buf) > 0 {
		msgpack.Unmarshal(buf, &(b.blocks))
	}
}

func (b *blockIndexFile) _save() error {
	buf, _ := msgpack.Marshal(b.blocks)
	return utils.WriteFile(b.path, buf, os.ModePerm)
}

func (b *blockIndexFile) _saveAppend(key string, block *blockIndex) error {
	return utils.AppendFile(b.path, b.getBlockBuf(key, block))
}

func (b *blockIndexFile) _saveAppends(vals map[string]*blockIndex) error {
	return utils.AppendFile(b.path, b.getBlockBufs(vals))
}

func (b *blockIndexFile) getBlockBuf(key string, block *blockIndex) []byte {
	blocks := map[string]*blockIndex{}
	blocks[key] = block
	buf, _ := msgpack.Marshal(blocks)
	return buf
}

func (b *blockIndexFile) getBlockBufs(vals map[string]*blockIndex) []byte {
	data, _ := msgpack.Marshal(vals)
	return data
}

func (b *blockIndexFile) getBlock(key string) *blockIndex {
	return b.blocks[key]
}

func (b *blockIndexFile) destroy() {
	os.Remove(b.path)
	b.blocks = map[string]*blockIndex{}
}
func (b *blockIndexFile) clean() {
	os.Truncate(b.path, 0)
	b.blocks = map[string]*blockIndex{}
}

func (b *blockIndexFile) add(key string, size int64) error {
	return b._saveAppend(key, b.addData(key, size))
}

func (b *blockIndexFile) adds(vals map[string]int64) error {
	return b._saveAppends(b.addDatas(vals))
}

func (b *blockIndexFile) addDatas(vals map[string]int64) (out map[string]*blockIndex) {
	var offset int64 = 0
	for _, bi := range b.blocks {
		offset += bi.Size
	}

	out = map[string]*blockIndex{}
	for key, size := range vals {
		block := &blockIndex{Offset: offset, Size: size}
		b.blocks[key] = block
		out[key] = block
	}
	return
}
func (b *blockIndexFile) addData(key string, size int64) *blockIndex {
	var offset int64 = 0
	for _, bi := range b.blocks {
		offset += bi.Size
	}
	block := &blockIndex{Offset: offset, Size: size}
	b.blocks[key] = block
	return block
}
func (b *blockIndexFile) set(key string, newSize int64) error {
	orgiBlock, ok := b.blocks[key]
	if !ok {
		return errors.New("not exist in block index")
	}

	if newSize == orgiBlock.Size {
		return nil
	}

	for _, block := range b.blocks {
		if block.Offset > orgiBlock.Offset {
			block.Offset += newSize - orgiBlock.Size
		}
	}
	orgiBlock.Size = newSize
	b._save()
	return nil
}

func (b *blockIndexFile) delete(key string) error {
	orgiBlock, ok := b.blocks[key]
	if !ok {
		return errors.New("not exist in block index")
	}
	for _, block := range b.blocks {
		if block.Offset > orgiBlock.Offset {
			block.Offset -= orgiBlock.Size
		}
	}
	delete(b.blocks, key)
	b._save()
	return nil
}
